{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "729fd42e-1e11-42ff-9057-c63bf231cc58",
   "metadata": {},
   "source": [
    "你可以在任意的路径操作中使用 response_model 参数来声明用于响应的模型：\n",
    "\n",
    "@app.get()\r\n",
    "@app.post()\r\n",
    "@app.put()\r\n",
    "@app.delete()\r\n",
    "\n",
    "注意，response_model是「装饰器」方法（get，post 等）的一个参数。不像之前的所有参数和请求体，它不属于路径操作函数。\n",
    "等等。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bc7deb82-02be-448d-a26d-92e44e50961d",
   "metadata": {},
   "source": [
    "它接收的类型与你将为 Pydantic 模型属性所声明的类型相同，因此它可以是一个 Pydantic 模型，\n",
    "\n",
    "但也可以是一个由 Pydantic 模型组成的 list，例如 List[Item]。\n",
    "\n",
    "FastAPI 将使用此 response_model 来：\r\n",
    "\r\n",
    "将输出数据转换为其声明的类型。\r\n",
    "校验数据。\r\n",
    "在 OpenAPI 的路径操作中为响应添加一个 JSON Schema。\r\n",
    "并在自动生成文档系统中使用。\r\n",
    "但最重要的是：\r\n",
    "\r\n",
    "会将输出数据限制在该模型定义内。下面我们会看到这一点有多重要。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6184e985-8d7e-4c71-8cff-e916aa1f78c7",
   "metadata": {},
   "source": [
    "响应模型在参数中被声明，而不是作为函数返回类型的注解，这是因为路径函数可能不会真正返回该响应模型，而是返回一个 dict、数据库对象或其他模型，然后再使用 response_model 来执行字段约束和序列化。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "084acd81-8f1d-426a-a7c8-7e5ac3b90265",
   "metadata": {},
   "source": [
    "我们可以创建一个有明文密码的输入模型和一个没有明文密码的输出模型："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "a8191df4-8697-45fe-8479-77b045bfffe1",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:     Started server process [11768]\n",
      "INFO:     Waiting for application startup.\n",
      "INFO:     Application startup complete.\n",
      "INFO:     Uvicorn running on http://0.0.0.0:8009 (Press CTRL+C to quit)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "INFO:     127.0.0.1:59846 - \"POST /user/ HTTP/1.1\" 422 Unprocessable Entity\n",
      "INFO:     127.0.0.1:59850 - \"POST /user/ HTTP/1.1\" 200 OK\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:     Shutting down\n",
      "INFO:     Waiting for application shutdown.\n",
      "INFO:     Application shutdown complete.\n",
      "INFO:     Finished server process [11768]\n"
     ]
    }
   ],
   "source": [
    "import uvicorn\n",
    "from fastapi import FastAPI\n",
    "from pydantic import BaseModel, EmailStr\n",
    "\n",
    "app = FastAPI()\n",
    "\n",
    "class UserIn(BaseModel):\n",
    "    username: str\n",
    "    password: str\n",
    "    email: EmailStr\n",
    "    full_name: str | None = None\n",
    "\n",
    "\n",
    "class UserOut(BaseModel):\n",
    "    username: str\n",
    "    email: EmailStr\n",
    "    full_name: str | None = None\n",
    "\n",
    "\n",
    "@app.post(\"/user/\", response_model=UserOut)\n",
    "async def create_user(user: UserIn):\n",
    "    return user\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    config = uvicorn.Config(app, host='0.0.0.0', port=8009)\n",
    "    server = uvicorn.Server(config)\n",
    "    await server.serve()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "652cd25f-aba1-4929-ad2f-969ffad30e84",
   "metadata": {},
   "outputs": [],
   "source": [
    "FastAPI 将会负责过滤掉未在输出模型中声明的所有数据（使用 Pydantic）。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e11e4d0f-b4e8-430b-80c6-f7e737cbbcc4",
   "metadata": {},
   "source": [
    "## requests库"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fe942f51-b89f-4fba-9db4-ce30b295d518",
   "metadata": {},
   "outputs": [],
   "source": [
    "url = 'http://127.0.0.1:8009/user/' \n",
    "data = {\n",
    "    \"username\": \"Foo\",\n",
    "    \"password\": \"1234321\",\n",
    "    \"email\": \"example@example.com\"\n",
    "}\n",
    "res = requests.post(url, json=data) \n",
    "res.text\n",
    "\n",
    "# 在另一个ipynb文件中运行代码，会得到 \n",
    "# '{\"username\":\"Foo\",\"email\":\"example@example.com\",\"full_name\":null}'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "967c2e9b-65d6-4006-bb6e-7e3f1544128b",
   "metadata": {},
   "source": [
    "## 响应模型编码参数"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6f0519cf-80ee-4c88-9d6d-e8b3a0c4cc56",
   "metadata": {},
   "source": [
    "你的响应模型可以具有默认值，例如："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "552b4081-41f0-4a2e-941e-34016d426d96",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:     Started server process [11768]\n",
      "INFO:     Waiting for application startup.\n",
      "INFO:     Application startup complete.\n",
      "INFO:     Uvicorn running on http://0.0.0.0:8009 (Press CTRL+C to quit)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "INFO:     127.0.0.1:60216 - \"GET /items/foo HTTP/1.1\" 200 OK\n",
      "INFO:     127.0.0.1:60221 - \"GET /items/bar HTTP/1.1\" 200 OK\n",
      "INFO:     127.0.0.1:60234 - \"GET /items/baz HTTP/1.1\" 200 OK\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:     Shutting down\n",
      "INFO:     Waiting for application shutdown.\n",
      "INFO:     Application shutdown complete.\n",
      "INFO:     Finished server process [11768]\n"
     ]
    }
   ],
   "source": [
    "import uvicorn\n",
    "from typing import List\n",
    "from fastapi import FastAPI\n",
    "from pydantic import BaseModel\n",
    "\n",
    "app = FastAPI()\n",
    "\n",
    "class Item(BaseModel):\n",
    "    name: str\n",
    "    description: str | None = None\n",
    "    price: float\n",
    "    tax: float = 10.5\n",
    "    tags: List[str] = []\n",
    "\n",
    "\n",
    "items = {\n",
    "    \"foo\": {\"name\": \"Foo\", \"price\": 50.2},\n",
    "    \"bar\": {\"name\": \"Bar\", \"description\": \"The bartenders\", \"price\": 62, \"tax\": 20.2},\n",
    "    \"baz\": {\"name\": \"Baz\", \"description\": None, \"price\": 50.2, \"tax\": 10.5, \"tags\": []},\n",
    "}\n",
    "\n",
    "\n",
    "@app.get(\"/items/{item_id}\", response_model=Item, response_model_exclude_unset=True)\n",
    "async def read_item(item_id: str):\n",
    "    return items[item_id]\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    config = uvicorn.Config(app, host='0.0.0.0', port=8009)\n",
    "    server = uvicorn.Server(config)\n",
    "    await server.serve()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1ff1ab8a-fd99-433a-bc84-530feba4de64",
   "metadata": {},
   "source": [
    "description: Union[str, None] = None 具有默认值 None。\n",
    "\n",
    "tax: float = 10.5 具有默认值 10.5.\n",
    "\n",
    "tags: List[str] = [] 具有一个空列表作为默认值：\n",
    "\n",
    "但如果它们并没有存储实际的值，你可能想从结果中忽略它们的默认值。\n",
    "\n",
    "你可以设置路径操作装饰器的 response_model_exclude_unset=True 参数：\n",
    "\n",
    "然后响应中将不会包含那些默认值，而是仅有实际设置的值。\n",
    "\n",
    "因此，如果你向路径操作发送 ID 为 foo 的商品的请求，则响应（不包括默认值）将为："
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cc298193-46d4-43b7-8c86-737fb62c3945",
   "metadata": {},
   "source": [
    "{\r\n",
    "    \"name\": \"Foo\",\r\n",
    "    \"price\": 50.2\r\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "26b68b60-5db6-403e-a1b0-4b81c996f826",
   "metadata": {},
   "outputs": [],
   "source": [
    "import requests\n",
    "url = 'http://127.0.0.1:8009/items/foo' \n",
    "res = requests.get(url) \n",
    "res.text\n",
    "\n",
    "# 在另一个ipynb文件中运行代码，会得到 '{\"name\":\"Foo\",\"price\":50.2}'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d6c7982a-1357-46ca-ad46-80cb9d6a7e8d",
   "metadata": {},
   "source": [
    "但是，如果你的数据在具有默认值的模型字段中有实际的值，例如 ID 为 bar 的项：\n",
    "\n",
    "{\r\n",
    "    \"name\": \"Bar\",\r\n",
    "    \"description\": \"The bartenders\",\r\n",
    "    \"price\": 62,\r\n",
    "    \"tax\": 20.2\r\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c567e565-c16d-4929-8b24-51aab19ae102",
   "metadata": {},
   "outputs": [],
   "source": [
    "url = 'http://127.0.0.1:8009/items/bar' \n",
    "res = requests.get(url) \n",
    "res.text\n",
    "\n",
    "# 在另一个ipynb文件中运行代码，会得到 '{\"name\":\"Bar\",\"description\":\"The bartenders\",\"price\":62.0,\"tax\":20.2}'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "52f6b562-edb4-45b2-a7ef-e46a24d24c59",
   "metadata": {},
   "source": [
    "如果数据具有与默认值相同的值，例如 ID 为 baz 的项：\n",
    "\n",
    "{\r\n",
    "    \"name\": \"Baz\",\r\n",
    "    \"description\": None,\r\n",
    "    \"price\": 50.2,\r\n",
    "    \"tax\": 10.5,\r\n",
    "    \"tags\": []\r\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "458fd74a-a79f-406c-b362-e7a48347b8cd",
   "metadata": {},
   "outputs": [],
   "source": [
    "url = 'http://127.0.0.1:8009/items/baz' \n",
    "res = requests.get(url) \n",
    "res.text\n",
    "\n",
    "# 在另一个ipynb文件中运行代码，会得到 '{\"name\":\"Baz\",\"description\":null,\"price\":50.2,\"tax\":10.5,\"tags\":[]}'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d8ed2b87-e479-4397-986d-4f890b7310a0",
   "metadata": {},
   "source": [
    "即使 description、tax 和 tags 具有与默认值相同的值，FastAPI 足够聪明 (实际上是 Pydantic 足够聪明) 去认识到这一点，它们的值被显式地所设定（而不是取自默认值）。\r\n",
    "\r\n",
    "因此，它们将包含在 JSON 响应中。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "78666172-5a9a-4b0b-813a-378099dfbf02",
   "metadata": {},
   "source": [
    "你还可以使用：\r\n",
    "\r\n",
    "response_model_exclude_defaults=True\r\n",
    "response_model_exclude_none=True"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c0fc56b0-d87d-4263-b0ff-bbeea2170b34",
   "metadata": {},
   "source": [
    "使用路径操作装饰器的 response_model 参数来定义响应模型，特别是确保私有数据被过滤掉。\r\n",
    "\r\n",
    "使用 response_model_exclude_unset 来仅返回显式设定的值。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
